const TMatrix = {

  "Mat":class Mat extends Array{
    // dats  矩阵数据源
    // shape 矩阵形状 [rows, cols]
    constructor(dats, shape){
      super();
      this._shape = [];

      if(dats) this.copyFrom(dats);

      if(shape){
        this._shape[0] = shape[0];
        this._shape[1] = shape[1];

        this.length    = shape[0] * shape[1];
      } 
    }
    // 从其他数组复制数据
    copyFrom(arr){
      this.length = 0;
      if(arr instanceof Array){
        this._shape[0] = arr.length;
        for(let i = 0, il = arr.length; i < il; i++){
          if(arr[i] instanceof Array){
            this._shape[1] = arr[i].length;
            for(let j = 0, jl = arr[i].length; j < jl; j++){
              this.push(parseFloat(arr[i][j]));
            }
          }
          else{
            this._shape[1] = 1;
            this.push(parseFloat(arr[i]));
          }
        }
      }
      else{
        this._shape[0] = 1;
        this._shape[1] = 1;
        this.push(parseFloat(arr));
      }
    }
    // 返回形状矩阵
    shape(){
      return this._shape;
    }
    rows(){
      return this._shape[0];
    }
    cols(){
      return this._shape[1];
    }
    // 修改形状
    reshape(rows=-1, cols=-1){
      if(rows <= 0 && cols <= 0) return;
      // 按列划分
      if(rows <= 0){
        let tRows = Math.floor(this.length / cols);
        if(tRows * cols !== this.length){
          console.error(`无效的形状`);
          return;
        }
        this._shape[0] = tRows;
        this._shape[1] = cols;
      }
      // 按行划分
      else if(cols <= 0){
        let tCols = Math.floor(this.length / rows);
        console.log(this.length, rows, tCols)
        if(tCols * rows !== this.length){
          console.error(`无效的形状`);
          return;
        }
        this._shape[0] = rows;
        this._shape[1] = tCols;
      }
      else{
        if(cols * rows !== this.length){
          console.error(`无效的形状`);
          return;
        }
        this._shape[0] = rows;
        this._shape[1] = cols;
      }
    }
    // 矩阵克隆
    clone(){
      console.log(typeof(this));
      let newMat = new Mat(this, this.shape());
      return newMat;
    }
    // 切分
    // 如果范围是个数字返回单行或单列
    // 否则返回范围制定的行和列
    slice(rowRange=undefined, colRange=undefined){
      // 返回整个矩阵
      if(rowRange === undefined && colRange === undefined){
        return this.clone();
      }

      let newMat    = new Mat();
      let beginRow  = 0;
      let endRow    = this.rows();
      let beginCol  = 0;
      let endCol    = this.cols();

      // 按行切分
      if(rowRange !== undefined){
        if(rowRange instanceof Array && rowRange.length === 2){
          beginRow = rowRange[0];
          endRow   = rowRange[1];
        }
        else if(parseInt(rowRange) >= 0){
          beginRow = parseInt(rowRange);
          endRow   = beginRow + 1;
        }
      }

      // 按列切分
      if(colRange !== undefined){
        if(colRange instanceof Array && colRange.length === 2){
          beginCol = colRange[0];
          endCol   = colRange[1];
        }
        else if(parseInt(colRange) >= 0){
          beginCol = parseInt(colRange);
          endCol   = beginCol + 1;
        }
      }
      // console.log("Slice:", beginRow, endRow, beginCol, endCol, this.rows(), this.cols());
      // 判断范围是否有效
      if(beginRow >= endRow || beginRow >= this.rows() || endRow > this.rows() || beginRow < 0){
        console.error("切分参数有误");
        return newMat;
      }
      if(beginCol >= endCol || beginCol >= this.cols() || endCol > this.cols() || beginCol < 0){
        console.error("切分参数有误");
        return newMat;
      }

      for(let i = beginRow; i < endRow; i++){
        for(let j = beginCol; j < endCol; j++){
          newMat.push(this[i * this.cols() + j]);
        }
      }
      newMat.reshape(endRow - beginRow, endCol - beginCol);

      return newMat;
    }
    
    // 转置
    T(){
      return this.transpose();
    }

    transpose(){
      let newMat = new Mat(this, [this.cols(), this.rows()])
      return newMat;
    }
    // 返回对应行列的值
    val(row, col){
      const index = row * this.cols() + col;
      if(index <= this.length){
        return this[index];
      }
      return NaN;
    }
    set(row, col, val){
      const index = row * this.cols() + col;
      if(index <= this.length){
        this[index] = val;
      }
    }
    // 最大值和最小值
    traverseAndFind(axis, func){
      let values = new Mat();

      for(let i = 0; i < this.rows(); i++){
        for(let j = 0; j < this.cols(); j++){
          // 列的最小值
          if(axis === 0){
            if(i===0){
              values[j] = this.val(i, j);
            }
            else{
              values[j] = func(values[j], this.val(i, j)); 
            }
          }
          // 行的最小值
          else if(axis === 1){
            if(j===0){
              values[i] = this.val(i, j);
            }
            else{
              values[i] = func(values[i], this.val(i, j)); 
            }
          }
          else{
            if(i === 0 && j === 0){
              values[0] = this.val(0, 0);
            }
            else{
              values[0] = func(values[0], this.val(i, j)); 
            }
          }
        }
      }

      if(axis === 0){
        values.reshape(1, this.cols());
        return values;
      }
      else if(axis === 1){
        values.reshape(1, this.rows());
        return values;
      }
      else{
        return values[0];
      }
    }
    min(axis){
      return this.traverseAndFind(axis, Math.min);
    }
    max(axis){
      return this.traverseAndFind(axis, Math.max);
    }
    // 自运算
    add(values){
      const tmp = TMatrix.add(this, values);
      for(let i = 0, il = this.length; i < il; i++){
        this[i] = tmp[i];
      }
    }
    multiply(values){
      const tmp = TMatrix.multiply(this, values);
      for(let i = 0, il = this.length; i < il; i++){
        this[i] = tmp[i];
      }
    }
    // 打印矩阵
    print(){
      console.log(this.toString());
    }

    toString(){
      var str = "[";
      
      for(let i = 0, il = this._shape[0]; i < il; i++){
        str += "[";
        console.log(this._shape[0]);
        console.log(this._shape[1]);
        for(let j = 0, jl = this._shape[1]; j < jl; j++){
          str += this[i * jl + j];
          if(j < jl - 1 ){
            str += ", ";
          }
        }
        str += "]";
        if(i < il - 1 ){
          str += ", ";
        }
      }
      str += "]";

      return str;
    }

    // 转化为数组
    toArray(){
      let arr = [];
      if(this.rows() === 1 || this.cols() === 1){
        arr = [...this];
      }
      else{
        for(let i = 0; i < this.rows(); i++){
          arr[i] = [];
          for(let j = 0; j < this.cols(); j++){
            arr[i][j] = this.val(i, j);
          }
        }
      }
      return arr;
    }
  },
  // 特殊矩阵
  "zeros":function(shape){
    let newMat = new TMatrix.Mat([], shape);
    newMat.fill(0.0);
    return newMat;
  },
  "ones":function(shape){
    let newMat = new TMatrix.Mat([], shape);
    newMat.fill(1.0);
    return newMat;
  },
  // 向量点乘
  "dot":function(vectA, vectB){
    if(vectA.length !== vectB.length){
      console.error('两向量长度不一致');
      return Number.NaN;
    }

    var sum = 0.0;
    for(let i = 0,  il = vectA.length; i < il; i++){
      sum += vectA[i]*vectB[i];
    }

    return sum;
  },
  // 矩阵乘法
  "mul":function(matA, matB){

    if(matA.cols() !== matB.rows()){
      return Number.NaN;
    }

    let newMat = new TMatrix.Mat();

    for(let r = 0; r < matA.rows(); r++){
      for(let c = 0; c < matB.cols(); c++){
        var tmp = 0.0;
        for(let i = 0; i < matA.cols(); i++){
          tmp += matA.val(r, i) * matB.val(i, c);
        }
        newMat.push(tmp);
      }
    }

    newMat.reshape(matA.rows(), matB.cols());

    return newMat;
  },
  // 一对一运算
  "oneByOneOperation":function(mat, values, operator){
    let newMat = new TMatrix.Mat();
    if(values instanceof Array && values.length === mat.length){
      for(let i = 0, il = mat.length; i < il; i++){
        newMat[i] = operator(mat[i], values[i]);
      }
      newMat.reshape(mat.rows(), mat.cols());
      return newMat;
    }
    else if(!isNaN(values)){
      const tmp = parseFloat(values);
      for(let i = 0, il = mat.length; i < il; i++){
        newMat[i] = operator(mat[i], tmp);
      }
      newMat.reshape(mat.rows(), mat.cols());
      return newMat;
    }

    return newMat;
  },
  // 加法
  "add":function(matA, matB){
    return TMatrix.oneByOneOperation(matA, matB, (a,b)=>{return a + b;})
  },
  // 减法
  "sub":function(matA, matB){
    return TMatrix.oneByOneOperation(matA, matB, (a,b)=>{return a - b;})
  },
  // 乘法
  "multiply":function(matA, matB){
    return TMatrix.oneByOneOperation(matA, matB, (a,b)=>{return a * b;})
  },
  // 除法
  "division":function(matA, matB){
    return TMatrix.oneByOneOperation(matA, matB, (a,b)=>{return a / b;})
  },

  _dim(arr){
    return this._depthSerchCountDim(arr, 0);
  },
  _depthSerchCountDim(arr, dim){
    if(arr instanceof Array && arr.length > 0){
      return this._depthSerchCountDim(arr[0], dim+1);
    }
    return dim;
  },

  "tileX":function(A, reps){
    let b = new this.Mat();
    if(A instanceof Array){
      if(this._dim(A) === 1){
        for(let n = 0; n < reps; n++){
          b.push(...A);
        }
        b._shape=[reps,A.length]
      }
      else if(this._dim(A) > 1){
        for(let i = 0, il = A.length; i < il; i++){
          b[i] = [];
          for(let n = 0; n < reps; n++){
            b[i].push(...A[i]);
          }
        }
      }
    }
    else{
      for(let n = 0; n < reps; n++){
        b.push(A)
      }
      b._shape=[1,reps]
    }
    return b;
  },
  "tileY":function(A, reps){
    let b = new this.Mat();
    if(A instanceof Array){
      if(this._dim(A) === 1){
        for(let i=0;i<A.length;i++){
          for(let n = 0; n < reps; n++){
            b.push(A[i]);
            // b[n] = [...A];
          }
        }
        b._shape = [A.length,reps]
      }
      else if(this._dim(A) > 1){
        for(let n = 0; n < reps; n++){
          for(let i = 0, il = A.length; i < il; i++){
            b[n * A.length + i] = [...A[i]];
          }
        }
      }
    }
    else{
      for(let n = 0; n < reps; n++){
        b[n] = [A];
      }
      b._shape = [reps,1]
    }
    return b;
  },
  "tile":function(A, reps){
    if(reps instanceof Array && reps.length === 2){
      let tmp = this.tileX(A, reps[1]);
      tmp = this.tileY(tmp, reps[0]);
      return new this.Mat(tmp);
    }
    else{
      return new this.Mat(this.tileX(A, reps));
    }
  }
}

export default TMatrix;
